package com.scut.dispatching_server.service.impl;

import com.scut.dispatching_server.config.GlobalVariables;
import com.scut.dispatching_server.model.RLOrderInfoForDispatching;
import com.scut.dispatching_server.service.RlDispatchingService;
import com.scut.dispatching_server.tool.DispatchingMatrixTool;
import com.scut.dispatching_server.tool.ManyToOneCalculation;
import com.scut.dispatching_server.tool.OneToManyCalculation;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Comparator;

@Component
public class RlManyToManyDispatching implements RlDispatchingService {
    /**保存每个订单在目前分配方案下的 R - V 值， R 是该订单在该轮所能获得的预期长期收益值，V 是该订单如果进入下一轮，其能够获得的预期长期收益值*/
    private float[] ordersGain;
    private int[][] orderDriverMatrix;
    private float[][][] probabilitiesOnetoOne;
    private float[][][] probabilitiesManytoOne;
    private RLOrderInfoForDispatching[] rlOrderInfos;

    public RlManyToManyDispatching(){

    }
    public float calculateSumGainSimply(ArrayList<Integer> preOrderIds, float sumGain){
        //首先计算每个订单在一对多情况下的概率
        ArrayList<float[]> oneToManyPD = calculateTool(preOrderIds);
        //对于每一个订单的最终概率，计算总收益
        float newPartGain = (float)0;
        for(int i = 0; i < preOrderIds.size(); i++){
            for(int j = 0; j < GlobalVariables.DISPATCHING_TIME_WINDOW; j++){
                newPartGain = newPartGain + oneToManyPD.get(i)[j] * (GlobalVariables.REWARD_FUNCTION.get(rlOrderInfos[preOrderIds.get(i)].getWaitRound() *
                        GlobalVariables.DISPATCHING_TIME_WINDOW + j) - rlOrderInfos[preOrderIds.get(i)].getExpectedValue());
            }
            sumGain = sumGain - ordersGain[preOrderIds.get(i)];
        }
        sumGain = sumGain  + newPartGain;
        return sumGain;
    }
    public float calculateSumGain(){
        float sumGain = 0f;
        ArrayList<float[]> oneToManyPD = new ArrayList<>();
        //首先计算每个订单在一对多情况下的概率
        for(int i = 0; i <= orderDriverMatrix[0].length - 1; i++){
            ArrayList<float[]> ordersProbabilities = new ArrayList<>();
            ArrayList<Integer> driverList = DispatchingMatrixTool.getDriversByOrderId(i, orderDriverMatrix);
            if(driverList.size() == 0){
                float[] zero = new float[GlobalVariables.DISPATCHING_TIME_WINDOW];
                oneToManyPD.add(zero);
                continue;
            }
            for(int j = 0; j <= driverList.size() - 1; j++){
                ordersProbabilities.add(probabilitiesOnetoOne[driverList.get(j)][i]);
            }
            oneToManyPD.add(OneToManyCalculation.getProbabilityDistribution(ordersProbabilities));
        }
        //对于每一个订单的最终概率，计算总收益
        for(int i = 0; i < oneToManyPD.size(); i++){
            float thisOrderGain = 0f;
            for(int j = 0; j < GlobalVariables.DISPATCHING_TIME_WINDOW; j++){
                sumGain = sumGain + oneToManyPD.get(i)[j] * (GlobalVariables.REWARD_FUNCTION.get(rlOrderInfos[i].getWaitRound() *
                        GlobalVariables.DISPATCHING_TIME_WINDOW + j) - rlOrderInfos[i].getExpectedValue());
                thisOrderGain = thisOrderGain + oneToManyPD.get(i)[j] * (GlobalVariables.REWARD_FUNCTION.get(rlOrderInfos[i].getWaitRound() *
                        GlobalVariables.DISPATCHING_TIME_WINDOW + j) - rlOrderInfos[i].getExpectedValue());
            }
            ordersGain[i] = thisOrderGain;
        }
        return sumGain;
    }
    //返回每个订单的一对多概率
    public ArrayList<float[]> calculateTool(ArrayList<Integer> orderIds){
        ArrayList<float[]> oneToManyPD = new ArrayList<float[]>();
        for(int i = 0; i < orderIds.size(); i++){
            ArrayList<float[]> ordersProbabilities = new ArrayList<float[]>();
            ArrayList<Integer> driverList = DispatchingMatrixTool.getDriversByOrderId(orderIds.get(i), orderDriverMatrix);
            if(driverList.size() == 0){
                float[] zero = new float[GlobalVariables.DISPATCHING_TIME_WINDOW];
                oneToManyPD.add(zero);
                continue;
            }
            for(int j = 0; j < driverList.size(); j++){
                ordersProbabilities.add(probabilitiesManytoOne[driverList.get(j)][orderIds.get(i)]);
                //System.out.println("Many to one length is " + probabilitiesManytoOne[driverList.get(j)][i].length);
            }
            //将每个订单的一对多概率保存在 oneToManyPD 中
            oneToManyPD.add(OneToManyCalculation.getProbabilityDistribution(ordersProbabilities));
        }
        return oneToManyPD;
    }
    public void changeOrdersGain(ArrayList<Integer> preOrderIds, int nowOrderId){
        //首先计算每个订单在一对多情况下的概率
        preOrderIds.add(nowOrderId);
        //对于每一个订单的最终概率，计算总收益
        ArrayList<float[]> oneToManyPD = calculateTool(preOrderIds);
        for(int i = 0; i < preOrderIds.size(); i++){
            float thisOrderGain = 0f;
            for(int j = 0; j < GlobalVariables.DISPATCHING_TIME_WINDOW; j++){
                thisOrderGain = thisOrderGain + oneToManyPD.get(i)[j] * (GlobalVariables.REWARD_FUNCTION.get(rlOrderInfos[preOrderIds.get(i)].getWaitRound() *
                        GlobalVariables.DISPATCHING_TIME_WINDOW + j) - rlOrderInfos[preOrderIds.get(i)].getExpectedValue());
            }
            ordersGain[preOrderIds.get(i)] = thisOrderGain;
        }
    }
    @Override
    public int[][] dispatching(int[][] originalOrderDriverMatrix, float[][][] originalProbabilitiesOnetoOne, RLOrderInfoForDispatching[] originalRlOrderInfos){
        this.orderDriverMatrix = new int[originalOrderDriverMatrix.length][originalOrderDriverMatrix[0].length];
        this.probabilitiesOnetoOne = originalProbabilitiesOnetoOne;
        //如果 probabilitiesManytoOne 和 probabilitiesOnetoOne 引用同一个 probabilitiesOnetoOne 的话，那么前者的修改也将导致后者的修改
        this.probabilitiesManytoOne = new float[originalOrderDriverMatrix.length][originalOrderDriverMatrix[0].length][GlobalVariables.DISPATCHING_TIME_WINDOW];
        float[][][] probabilitiesOnetoOneTimeRanges = new float[originalOrderDriverMatrix.length][originalOrderDriverMatrix[0].length][GlobalVariables.RESPONSE_TIME_WINDOWS.length];
        //分配期间该属性并不会被修改，分配完成后该属性才会被修改
        this.rlOrderInfos = originalRlOrderInfos;
        for(int i = 0; i <= originalOrderDriverMatrix.length - 1; i++){
            for(int j = 0; j <= originalOrderDriverMatrix[0].length - 1; j++){
                this.orderDriverMatrix[i][j] = originalOrderDriverMatrix[i][j];
                for(int m = 0; m <= GlobalVariables.DISPATCHING_TIME_WINDOW - 1; m++){
                    this.probabilitiesManytoOne[i][j][m] = originalProbabilitiesOnetoOne[i][j][m];
                }
                for(int r = 0; r < GlobalVariables.RESPONSE_TIME_WINDOWS.length - 1; r++) {
                    for(int m = GlobalVariables.RESPONSE_TIME_WINDOWS[r]; m < GlobalVariables.RESPONSE_TIME_WINDOWS[r + 1]; m++){
                        probabilitiesOnetoOneTimeRanges[i][j][r] = probabilitiesOnetoOneTimeRanges[i][j][r] + this.probabilitiesManytoOne[i][j][m];
                    }
                }
            }
        }
        //存储每个订单在多对多情况下的收益
        ordersGain = new float[orderDriverMatrix[0].length];
        //开始调整
        float sumGain = calculateSumGain();
        System.out.println("初始收益为" + sumGain);
        for(int j = 0; j <= orderDriverMatrix[0].length - 1; j++){
            //所有没有接收到订单 j 的司机
            ArrayList<Integer> drivers = DispatchingMatrixTool.getDriversWhoDidNotReceiveThisOrder(j, orderDriverMatrix);
            for(int i = 0; i <= drivers.size() - 1; i++){
                int k = drivers.get(i);
                //如果司机 k 所在位置在订单 j 的最大接收范围之外
//                if(orderDriverMatrix[k][j] == 2){
//                    continue;
//                }
                //如果增加策略提升了总收益，则该标志位为 true
                boolean flag = false;
                //目前该司机被分配到的订单
                ArrayList<Integer> orderIds = DispatchingMatrixTool.getDemandIndexesBySupplierIndex(k, orderDriverMatrix);
                int[] tmp = new int[orderDriverMatrix[0].length];
                int[] finalMatrix = new int[orderDriverMatrix[0].length];
                //目前司机k的订单接收情况
                for(int m = 0; m <= orderDriverMatrix[0].length - 1; m++){
                    tmp[m] = orderDriverMatrix[k][m];
//                    finalMatrix[m] = orderDriverMatrix[k][m];
                }
                float tmpSumGain = sumGain;
                //司机 k 在多对一下对各订单响应时间的概率分布
                float[][] tmpProbabilitiesManyToOne = new float[orderDriverMatrix[0].length][GlobalVariables.DISPATCHING_TIME_WINDOW];
                float[][] finalProbabilitiesManyToOne = new float[orderDriverMatrix[0].length][GlobalVariables.DISPATCHING_TIME_WINDOW];
                //暂时在里面保存多对一情况下司机 k 对所有订单响应时间的概率分布
                for(int m = 0; m <= orderDriverMatrix[0].length - 1; m++){
                    for(int n = 0; n < GlobalVariables.DISPATCHING_TIME_WINDOW; n++){
                        tmpProbabilitiesManyToOne[m][n] = probabilitiesManytoOne[k][m][n];
                    }
                }
                //增加策略
                if(orderIds.size() < GlobalVariables.UPPER_LIMIT_ONE_DRIVER_CAN_RECEIVE_ORDERS){
                    //将目前这个订单加入到司机的订单列表中
                    orderDriverMatrix[k][j] = 1;
                    //后续优化
                    ArrayList<Integer> orderIdsInManyToOne = DispatchingMatrixTool.getDemandIndexesBySupplierIndex(k, orderDriverMatrix);
                    for(int jj = 0; jj < orderIdsInManyToOne.size(); jj++){
                        int thisOrderId = orderIdsInManyToOne.get(jj);
                        ArrayList<float[]> ordersProbablitiesExceptThisOrder = new ArrayList<>();
                        for(int orderIndex = 0; orderIndex < orderIdsInManyToOne.size(); orderIndex++){
                            if(orderIdsInManyToOne.get(orderIndex).equals(thisOrderId)){
                                continue;
                            }
                            ordersProbablitiesExceptThisOrder.add(probabilitiesOnetoOneTimeRanges[k][orderIdsInManyToOne.get(orderIndex)]);
                        }
                        float[] answerTimeProbabilities = ManyToOneCalculation.getProbabilityDistribution(probabilitiesOnetoOneTimeRanges[k][thisOrderId], ordersProbablitiesExceptThisOrder);
                        for(int s = 0; s < GlobalVariables.DISPATCHING_TIME_WINDOW; s++) {
                            probabilitiesManytoOne[k][thisOrderId][s] = answerTimeProbabilities[s];
                        }
                    }
                    float gainNow = calculateSumGainSimply(orderIdsInManyToOne, sumGain);
                    if(gainNow > tmpSumGain){
                        flag = true;
                        tmpSumGain = gainNow;
                        //暂存多对一的概率
                        for(int m = 0; m < orderDriverMatrix[0].length; m++){
                            for(int n = 0; n < GlobalVariables.DISPATCHING_TIME_WINDOW; n++){
                                finalProbabilitiesManyToOne[m][n] = probabilitiesManytoOne[k][m][n];
                            }
                        }
                        //暂存分配矩阵
                        for(int m = 0; m <= orderDriverMatrix[0].length - 1; m++){
                            finalMatrix[m] = orderDriverMatrix[k][m];
                        }
                    }
                }
                //恢复多对一的概率
                for(int m = 0; m < orderDriverMatrix[0].length; m++){
                    for(int n = 0; n < GlobalVariables.DISPATCHING_TIME_WINDOW; n++){
                        probabilitiesManytoOne[k][m][n] = tmpProbabilitiesManyToOne[m][n];
                    }
                }
                //恢复分配矩阵
                for(int m = 0; m <= orderDriverMatrix[0].length - 1; m++){
                    orderDriverMatrix[k][m] = tmp[m];
                }
                /*替换策略
                启发式替换，从替换一个到全部替换。首先在多对一概率矩阵的基础上，得到 orderIds 中各 order 在司机 k 那里的多对一概率占各
                 order 总多对一概率之和的比例(注意比例为 1 的情况)，按该比例的大小对各 order 进行排序从小到大排好序后，开始替换。假设
                 排好序后的 orderIds 为 [5, 1, 4, 2, 10], 则有五种启发式替换策略，分别为 [5], [5, 1], [5, 1, 4], [5, 1, 4, 2],
                 [5, 1, 4, 2, 10]。*/
                ArrayList<OrderIdAndRatioOfThisDriver> orderIdAndRatioOfThisDriverArrayList = new ArrayList<OrderIdAndRatioOfThisDriver>();
                for(int orderIndex = 0; orderIndex < orderIds.size(); orderIndex++){
                    int thisOrderId = orderIds.get(orderIndex);
                    ArrayList<Integer> driversWhoReceiveThisOrder = DispatchingMatrixTool.getDriversByOrderId(thisOrderId, orderDriverMatrix);
                    float sumProbabilitiesOfThisOrderInManyToOne = 0f;
                    for(int driverIndex = 0; driverIndex < driversWhoReceiveThisOrder.size(); driverIndex++){
                        sumProbabilitiesOfThisOrderInManyToOne = sumProbabilitiesOfThisOrderInManyToOne +
                                DispatchingMatrixTool.getSumProbability(probabilitiesManytoOne[driversWhoReceiveThisOrder.get(driverIndex)][thisOrderId]);
                    }
                    float thisDriverProbabilityInManyToOne = DispatchingMatrixTool.getSumProbability(probabilitiesManytoOne[k][thisOrderId]);
                    OrderIdAndRatioOfThisDriver orderIdAndRatioOfThisDriver =
                            new OrderIdAndRatioOfThisDriver(thisOrderId,
                                    (thisDriverProbabilityInManyToOne / sumProbabilitiesOfThisOrderInManyToOne) *
                                            (GlobalVariables.REWARD_FUNCTION.get(rlOrderInfos[thisOrderId].getWaitRound() * GlobalVariables.DISPATCHING_TIME_WINDOW) -
                                                    rlOrderInfos[thisOrderId].getExpectedValue()));
                    orderIdAndRatioOfThisDriverArrayList.add(orderIdAndRatioOfThisDriver);
                }
                orderIdAndRatioOfThisDriverArrayList.sort(Comparator.comparing(OrderIdAndRatioOfThisDriver::getRatio));
                for(int replaceOrderNums = 1; replaceOrderNums <= orderIds.size(); replaceOrderNums++){
                    ArrayList<Integer> remainingOrderIds = new ArrayList<>();
                    remainingOrderIds.addAll(orderIds);
                    remainingOrderIds.add(j);
                    for(int replaceOrderIndex = 0; replaceOrderIndex < replaceOrderNums; replaceOrderIndex++){
                        int replacedOrderId = orderIdAndRatioOfThisDriverArrayList.get(replaceOrderIndex).getOrderId();
                        remainingOrderIds.remove((Integer)replacedOrderId);
                        orderDriverMatrix[k][replacedOrderId] = 0;
                        float[] zero = new float[GlobalVariables.DISPATCHING_TIME_WINDOW];
                        probabilitiesManytoOne[k][replacedOrderId] = zero;
                    }
                    orderDriverMatrix[k][j] = 1;
                    for(int jj = 0; jj < remainingOrderIds.size(); jj++){
                        int thisOrderId = remainingOrderIds.get(jj);
                        ArrayList<float[]> ordersProbablitiesExceptThisOrder = new ArrayList<float[]>();
                        for(int orderIndex = 0; orderIndex < remainingOrderIds.size(); orderIndex++){
                            if(remainingOrderIds.get(orderIndex).equals(thisOrderId)){
                                continue;
                            }
                            ordersProbablitiesExceptThisOrder.add(probabilitiesOnetoOneTimeRanges[k][remainingOrderIds.get(orderIndex)]);
                        }
                        float[] answerTimeProbabilities = ManyToOneCalculation.getProbabilityDistribution(probabilitiesOnetoOneTimeRanges[k][thisOrderId], ordersProbablitiesExceptThisOrder);
                        for(int s = 0; s < GlobalVariables.DISPATCHING_TIME_WINDOW; s++) {
                            probabilitiesManytoOne[k][thisOrderId][s] = answerTimeProbabilities[s];
                        }
                    }
                    ArrayList<Integer> changedOrderIds = new ArrayList<>();
                    changedOrderIds.add(j);
                    changedOrderIds.addAll(orderIds);
                    float gainNow = calculateSumGainSimply(changedOrderIds, sumGain);
                    if(gainNow > tmpSumGain){
//                        System.out.println("替换");
                        flag = true;
                        tmpSumGain = gainNow;
                        //暂存多对一的概率
                        for(int m = 0; m < orderDriverMatrix[0].length; m++){
                            for(int n = 0; n < GlobalVariables.DISPATCHING_TIME_WINDOW; n++){
                                finalProbabilitiesManyToOne[m][n] = probabilitiesManytoOne[k][m][n];
                            }
                        }
                        //暂存分配矩阵
                        for(int m = 0; m < orderDriverMatrix[0].length; m++){
                            finalMatrix[m] = orderDriverMatrix[k][m];
                        }
                    }
                    //恢复多对一的概率
                    for(int m = 0; m < orderDriverMatrix[0].length; m++){
                        for(int n = 0; n < GlobalVariables.DISPATCHING_TIME_WINDOW; n++){
                            probabilitiesManytoOne[k][m][n] = tmpProbabilitiesManyToOne[m][n];
                        }
                    }
                    //恢复分配矩阵
                    for(int m = 0; m < orderDriverMatrix[0].length; m++){
                        orderDriverMatrix[k][m] = tmp[m];
                    }
                }
                if(flag){
                    //最终分配矩阵
                    for(int m = 0; m <= orderDriverMatrix[0].length - 1; m++){
                        orderDriverMatrix[k][m] = finalMatrix[m];
                    }
                    sumGain = tmpSumGain;
                    //最终多对一的概率
                    for(int m = 0; m < orderDriverMatrix[0].length; m++){
                        for(int n = 0; n < GlobalVariables.DISPATCHING_TIME_WINDOW; n++){
                            probabilitiesManytoOne[k][m][n] = finalProbabilitiesManyToOne[m][n];
                        }
                    }
                    changeOrdersGain(orderIds ,j);
                }
            }
        }
        System.out.println(sumGain);
        return orderDriverMatrix;
    }
    class OrderIdAndRatioOfThisDriver{
        private int orderId;
        private float ratio;
        public OrderIdAndRatioOfThisDriver(int orderId, float ratio){
            this.orderId = orderId;
            this.ratio = ratio;
        }

        public float getRatio() {
            return ratio;
        }

        public int getOrderId() {
            return orderId;
        }
    }
}
